---
title: Дерево каталогов со ссылками
description: Напишем скрипт Bash для построения дерева каталогов для репозитория в файле Markdown. Будем использовать только средства Bash и базовое ПО Linux без...
sections: [Рекурсия,Обработка файлов,Вёб-навигация]
tags: [linux,bash,markdown,html,структура,каталоги,файлы,ссылки,сортировка]
canonical_url: /ru/2023/08/03/directory-tree.html
url_translated: /en/2023/08/04/directory-tree.html
title_translated: Directory tree with links
date: 2023.08.03
---

Напишем скрипт Bash для построения дерева каталогов для репозитория в
файле Markdown. Будем использовать только средства Bash и базовое ПО Linux
— `ls`, `sed`, `tr` и `echo` — без дополнительных программ. Полученный файл
[`DIRECTORY_TREE.md`]({{ site.homepage_url }} "{{ site.homepage_name }}")
будем использовать в вёб-интерфейсе для навигации по объектам репозитория.

Создаём рекурсивную функцию и с её помощью обходим все файлы и каталоги репозитория, за
исключением списка из `.gitignore` — строим структуру каталогов в форме дерева. Выводим
элементы в виде ссылок `<a>`, сворачиваем папки с одним вложенным элементом в одну строку,
помещаем собранное дерево в контейнер `<pre>` и добавляем заголовок — в результате получаем
краткий и лаконичный файл Markdown со ссылками.

```bash
#!/bin/bash
# отсортированный список файлов и каталогов
function list_directory_contents {
  # сначала заглавные буквы, потом строчные, сначала каталоги, потом файлы
  eval "LC_COLLATE=C ls -A --group-directories-first $exclusions $1"
}
# дерево каталогов со ссылками
function directory_tree {
  # аргументы
  local path="$1"
  local head="$2"
  local tail="$3"
  # префикс для текущего элемента
  if [ "one" == "$4" ]; then
    echo -n "/"
  else
    echo -ne "\n$head"
  fi
  # текущий элемент дерева
  echo -n "<a href='${path#*/}'>${path##*/}</a>"
  # рекурсивные вызовы для подкаталогов
  if [ -d "$path" ]; then
    local list # массив файлов и каталогов
    readarray -t list <<<"$(list_directory_contents "$path")"
    local len=${#list[@]} # размер массива
    local i # счётчик
    for ((i = 0; i < len; i++)); do
      if [ -z "${list[$i]}" ]; then
        continue # пропустить пустой каталог
      elif ((len == 1)); then
        directory_tree "$path/${list[$i]}" "$tail" "$tail" "one"
      elif ((i < len - 1)); then
        directory_tree "$path/${list[$i]}" "$tail├─ " "$tail│  "
      else
        directory_tree "$path/${list[$i]}" "$tail└─ " "$tail   "
      fi
    done
  fi
}
# строка исключений для 'ls' из списка '.gitignore' — неотслеживаемые файлы
exclusions="-I \".git\" $(sed 's/^/ -I "/;s/$/"/' .gitignore | tr -d '\n')"
# помещаем дерево в контейнер, добавляем заголовок и выводим в файл
{
  echo "## Дерево каталогов"
  echo -ne "\n<pre>"
  directory_tree .
  echo -e "\n</pre>"
} >DIRECTORY_TREE.md
```

Запускаем скрипт в корне репозитория и сохраняем полученный файл.
